home *** CD-ROM | disk | FTP | other *** search
/ Nebula 1 / Nebula One.iso / ArchiveUtils / PackageInspector-0.96 / Source / PackageInspector.m < prev    next >
Text File  |  1996-03-18  |  14KB  |  514 lines

  1. /*+++*
  2.  *  title:    PackageInspector.m
  3.  *  abstract:    NEXTSTEP Workspace Manager Inspector for Installer ".pkg" files.
  4.  *  author:    T.R.Hageman, Groningen, The Netherlands
  5.  *  created:    November 1994
  6.  *  modified:    (see RCS Log at end)
  7.  *  copyleft:
  8.  *
  9.  *        Copyright (C) 1994--1996  Tom R. Hageman.
  10.  *
  11.  *    This is free software; you can redistribute it and/or modify
  12.  *    it under the terms of the GNU General Public License as published by
  13.  *    the Free Software Foundation; either version 2 of the License, or
  14.  *    (at your option) any later version.
  15.  *
  16.  *    This software is distributed in the hope that it will be useful,
  17.  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
  18.  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  19.  *    GNU General Public License for more details.
  20.  *
  21.  *    You should have received a copy of the GNU General Public License
  22.  *    along with this software; if not, write to the Free Software
  23.  *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  24.  *
  25.  *  description:
  26.  *
  27.  *---*/
  28.  
  29. #ifdef RCS_ID
  30. static const char RCSid[] =
  31. "PackageInspector.m,v 1.9 1996/03/18 22:02:53";
  32. #endif
  33.  
  34. #define VERSION    "0.96"
  35.  
  36. #ifndef DEBUG
  37. #   define DEBUG 0
  38. #endif
  39. #define LISTCONTENTS    0    // List Contents not yet implemented
  40.  
  41. #import "PackageInspector.h"
  42. #include <string.h>
  43. #include <stdlib.h>
  44. #include <stdio.h>
  45.  
  46. // Localized strings
  47. #define OPENBUTTON        NXLocalizedStringFromTableInBundle(NULL, bundle, "Open", NULL, button label)
  48. #define LISTCONTENTSBUTTON    NXLocalizedStringFromTableInBundle(NULL, bundle, "List Contents", NULL, button label)
  49. #define LISTDESCRIPTIONBUTTON    NXLocalizedStringFromTableInBundle(NULL, bundle, "Description", NULL, button label)
  50.  
  51. // States
  52. #define STATE_UNINSTALLED    NXLocalizedStringFromTableInBundle(NULL, bundle, "Uninstalled", NULL, original package state)
  53. #define STATE_INSTALLED        NXLocalizedStringFromTableInBundle(NULL, bundle, "installed", "Installed", package has been uncompressed unto disk)
  54. #define STATE_COMPRESSD        NXLocalizedStringFromTableInBundle(NULL, bundle, "compressed", "Compressed", installed package has been recompressed)
  55.  
  56. // so InfoView.strings can be ripped off from Installer.app
  57. #define SIZEFORMAT        NXLocalizedStringFromTableInBundle("InfoView", bundle, "%s installed, %s compressed", NULL, Short indication to user about the size of a package once installed and the size when compressed)
  58. #define KBYTES            NXLocalizedStringFromTableInBundle("InfoView", bundle, "KB", NULL, Kilobytes -- package size)
  59. #define MBYTES            NXLocalizedStringFromTableInBundle("InfoView", bundle, "MB", NULL, MegaBytes -- package size)
  60.  
  61. #define SIZEGZIPFORMAT        NXLocalizedStringFromTableInBundle(NULL, bundle, "%s installed, %s gzipped", NULL, Short indication to user about the size of a package once installed and the size when compressed using gzip_package)
  62.  
  63. #define LOCALIZE(s)        NXLoadLocalizedStringFromTableInBundle(NULL, bundle, s, NULL)
  64. #define LOCALIZE_ARCH(s)    NXLoadLocalizedStringFromTableInBundle("Architectures", bundle, s, NULL)
  65.  
  66.  
  67. @implementation PackageInspector
  68.  
  69. +new
  70. {
  71.     static PackageInspector *instance;
  72.     
  73.     if (instance == nil) {
  74.         char path[MAXPATHLEN+1];
  75.         const char *nibname = [self name];
  76.  
  77.         instance = [super new];
  78.  
  79.         instance->bundle = [NXBundle bundleForClass:self];
  80.  
  81.         if ([instance->bundle getPath:path forResource:nibname ofType:"nib"] &&
  82.             [NXApp loadNibFile:path owner:instance]) {
  83.             [instance->inspectorVersionField setStringValue:VERSION];
  84.             [instance->packageDescriptionText setVertResizable:YES]; // ??Necessary??
  85.         }
  86.         else {
  87.                 fprintf(stderr, "Couldn't load %s.nib\n", nibname);
  88.             [instance free];
  89.             instance = nil;
  90.         }
  91.     }
  92.     return instance;
  93. }
  94.  
  95. -showInfo:sender
  96. {
  97.     if (infoPanel == nil) {
  98.         char path[MAXPATHLEN+1];
  99.  
  100.         if ([bundle getPath:path forResource:"Info" ofType:"nib"] &&
  101.             [NXApp loadNibFile:path owner:self]) {
  102.             [infoVersionField setStringValue:[inspectorVersionField stringValue]];
  103.         }
  104.     }
  105.     [infoPanel makeKeyAndOrderFront:sender];
  106.     return self;
  107. }
  108.  
  109. -revert:sender
  110. {
  111.     [super revert:sender];
  112.  
  113.     if ([self selectionCount] != 1) {
  114.         return nil;
  115.     }
  116.     if (sender == [self revertButton]) {
  117.         [self toggleDescription];
  118.     }
  119.     else {
  120.         char path[MAXPATHLEN+1];
  121.  
  122.         [package free];
  123.         [self selectionPathsInto:path separator:'\0'];
  124.         if (!(package = [[NXBundle allocFromZone:[self zone]] initForDirectory:path])) {
  125.             return nil;
  126.         }
  127.         if ([self shouldLoad]) {
  128.             [self load];
  129.             revertButtonState = listContents;
  130.         }
  131.     }
  132.     [[[self okButton] setTitle:OPENBUTTON] setEnabled:YES];
  133.     [self setRevertButtonTitle];
  134.  
  135.     return self;
  136. }
  137.  
  138. -ok:sender
  139. {
  140.     [self perform:@selector(open:) with:sender afterDelay:0 cancelPrevious:NO];
  141.     [super ok:sender];
  142.     return self;
  143. }
  144.  
  145. -load
  146. {
  147.     char buf[256], size[2][20];
  148.     HashTable *table = [[HashTable alloc] initKeyDesc:"*" valueDesc:"*"];
  149.  
  150.     [self getArchs];
  151.     // Collect information about the package in a hashtable.
  152.     [self loadKeyValuesFrom:"info" inTable:table];
  153.     [self loadKeyValuesFrom:"sizes" inTable:table];
  154.     [self loadContentsOf:"location" inTable:table];
  155.     [self loadContentsOf:"status" inTable:table];
  156.  
  157.     // Convenience macro.
  158. #define LOOKUP(key, notfound)    ([table isKey:key] ? [table valueForKey:key] : \
  159.                  (notfound))
  160. #if 0
  161.     // Set the various controls.
  162.     sprintf(buf, "<<not yet implemented>>");
  163.     // Well then, how *DOES* Installer determine this??? 
  164.     [packageArchesField setStringValue:buf];
  165. #endif
  166.     [packageDescriptionText setText:LOOKUP("Description", "")];
  167.     [packageLocationField setStringValue:
  168.      LOOKUP("location", LOOKUP("DefaultLocation", "???"))];
  169.  
  170.     [self formatSize:[table valueForKey:"InstalledSize"] inBuf:size[0]];
  171.     [self formatSize:[table valueForKey:"CompressedSize"] inBuf:size[1]];
  172.     // [TRH] this knows gzip_package internals... 
  173.     sprintf(buf, [table isKey:"realCompressedSize"] ? SIZEGZIPFORMAT : SIZEFORMAT,
  174.         size[0], size[1]);
  175.     [packageSizesField setStringValue:buf];
  176.  
  177.     [packageStatusField setStringValue:LOCALIZE(LOOKUP("status", "Uninstalled"))];
  178.     [packageTitleField setStringValue:LOOKUP("Title", "???")];
  179.     [packageVersionField setStringValue:LOOKUP("Version", "???")];
  180. #undef LOOKUP
  181.     // Is this how one frees the contents of a hashtable?
  182.     [table freeKeys:free values:free];
  183.     [table free];
  184.  
  185.     [self loadImage];
  186.  
  187.     return self;
  188. }
  189.  
  190. -loadKeyValuesFrom:(const char *)type inTable:(HashTable *)table
  191. {
  192.     char path[MAXPATHLEN+1];
  193.     NXStream *stream;
  194.  
  195.     if (stream = NXMapFile([self getPath:path forType:type], NX_READONLY)) {
  196.         int c;
  197.  
  198. #if DEBUG & 1
  199.         fprintf(stderr, "loadKeyValuesFrom:%s\n", path);
  200. #endif
  201.         while ((c = NXGetc(stream)) >= 0) {
  202.             // Buffer sizes should be enough, according to doc.
  203.             char key[1024+1], value[1024+1];
  204.             char *p;
  205.  
  206.             if (NXIsSpace(c)) continue;
  207.             if (c == '#') {
  208.                 while ((c = NXGetc(stream)) >= 0 && c != '\n') ;
  209.                 continue;
  210.             }
  211.             // Found key; collect it.
  212.             p = key;
  213.             do {
  214.                 if (p < &key[sizeof key-1]) *p++ = c;
  215.             } while ((c = NXGetc(stream)) >= 0 && !NXIsSpace(c));
  216.             *p = '\0';
  217.  
  218.             // Skip over spaces and tabs.
  219.             while (c == ' ' || c == '\t') c = NXGetc(stream);
  220.  
  221.             // Value is rest of line, up to newline.
  222.             p = value;
  223.             do {
  224.                 if (p < &value[sizeof value-1]) *p++ = c;
  225.             } while ((c = NXGetc(stream)) >= 0 && c != '\n');
  226.             *p = '\0';
  227.  
  228.             // Insert key/value pair in hashtable.
  229. #if DEBUG & 1
  230.             fprintf(stderr, "key:%s value:%s\n", key, value);
  231. #endif
  232.             [table insertKey:NXCopyStringBuffer(key)
  233.              value:NXCopyStringBuffer(value)];
  234.         }
  235.  
  236.         NXCloseMemory(stream, NX_FREEBUFFER);
  237.     }
  238.     return self;
  239.  
  240. }
  241.  
  242. -loadContentsOf:(const char *)type inTable:(HashTable *)table
  243. {
  244.     char path[MAXPATHLEN+1];
  245.     NXStream *stream;
  246.  
  247.     if (stream = NXMapFile([self getPath:path forType:type], NX_READONLY)) {
  248.         char line[1024+1];
  249.         int n = NXRead(stream, line, sizeof line);
  250.  
  251.         if (n > 0 && line[n-1] == '\n') line[n-1] = '\0';    // remove trailing newline.
  252.  
  253.         NXCloseMemory(stream, NX_FREEBUFFER);
  254.  
  255.         [table insertKey:NXCopyStringBuffer(type)
  256.          value:NXCopyStringBuffer(line)];
  257.     }
  258.     return self;
  259. }
  260.  
  261. -loadImage
  262. {
  263.     char path[MAXPATHLEN+1];
  264.     NXImage *image;
  265.  
  266.     // Remove old image from the button.
  267.     if (image = [packageIconButton image]) {
  268.         [packageIconButton setImage:nil];
  269.         [image free];
  270.     }
  271.     // Get the image (if any) from the package
  272.     image = [[NXImage allocFromZone:[self zone]] initFromFile:[self getPath:path forType:"tiff"]];
  273.     [packageIconButton setImage:image];
  274.  
  275.     return self;
  276. }
  277.  
  278.  
  279. #define STAT_EQ(s1, s2)    ((s1)->st_ino == (s2)->st_ino && \
  280.              (s1)->st_dev == (s2)->st_dev && \
  281.              (s1)->st_mtime == (s2)->st_mtime && \
  282.              (s1)->st_size == (s2)->st_size)
  283.  
  284. -(BOOL)shouldLoad
  285. {
  286.     char path[MAXPATHLEN+1];
  287.     struct stat newstats[NUMSTATS];
  288.     static const char * const typesToStat[NUMSTATS] = { TYPESTOSTAT };
  289.     BOOL result = NO;
  290.     int i;
  291.  
  292.     for (i = 0;  i < NUMSTATS;  i++) {
  293.         memset(&newstats[i], 0, sizeof(struct stat));
  294.         if (!(stat([self getPath:path forType:typesToStat[i]], &newstats[i]) == 0 &&
  295.               STAT_EQ(&newstats[i], &stats[i]))) {
  296.             result = YES;
  297.             ///break; // NOT!!! must stat all for accurate cache.
  298.         }
  299.         stats[i] = newstats[i];
  300.     }
  301.  
  302.     return result;
  303. }
  304.  
  305. -toggleDescription
  306. {
  307.     switch (revertButtonState) {
  308.     case listContents:
  309.         // TODO: swap views?
  310.         revertButtonState = listDescription;
  311.         break;
  312.     case listDescription:
  313.         revertButtonState = listContents;
  314.         break;
  315.     }
  316.     return [self setRevertButtonTitle];
  317. }
  318.  
  319.  
  320. // Support methods
  321. -(const char *)getPath:(char *)buf forType:(const char *)type
  322. {
  323.     char name[MAXPATHLEN+1];
  324.  
  325.     // Get package name, sans extension.
  326.     *strrchr(strcpy(name, strrchr([package directory], '/')+1), '.') = '\0';
  327.  
  328.     // Now get the full pathname.
  329.     [package getPath:buf forResource:name ofType:type];
  330. #if DEBUG & 2
  331.     fprintf(stderr, "PackageInspector: type=\"%s\" name=\"%s\" path=\"%s\"\n",
  332.         type, name, buf);
  333. #endif
  334.     return buf;
  335. }
  336.  
  337. -setRevertButtonTitle
  338. {
  339. #if LISTCONTENTS
  340.     [[[self revertButton]
  341.       setTitle:LOCALIZE(revertButtonState == listContents ?
  342.                 "List Contents" : "Description")]
  343.      setEnabled:YES];
  344. #endif
  345.     return self;
  346. }
  347.  
  348. -(const char *)formatSize:(const char *)size inBuf:(char *)buf
  349. {
  350.     // [TRH] this is very simplistic (but seems consistent with Installer.app)
  351.     if (!size) {
  352.         strcpy(buf, "???");
  353.     }
  354.     else {
  355.         int len = strlen(size);
  356.         if (len < 4) {
  357.             sprintf(buf, "%s%s", size, KBYTES);
  358.         }
  359.         else if (len < 6) {
  360.             sprintf(buf, "%.*s.%.*s%s",
  361.                 (len-3), size, 3-(len-3), size+(len-3), MBYTES); 
  362.         }
  363.         else {
  364.             sprintf(buf, "%.*s%s", (len-3), size, MBYTES);
  365.         }
  366.     }
  367.     return buf;
  368. }
  369.  
  370. // Determine architectures, in separate subprocess.
  371.  
  372. #define WORKING    " ..."    // `I'm still busy' indicator.
  373.  
  374. -(void)getArchs
  375. {
  376.     char command[2*MAXPATHLEN+10+1];
  377.  
  378.     if (archProcess) [archProcess terminate:self];
  379.  
  380.     [packageArchesField setStringValue:WORKING];
  381.  
  382.     [bundle getPath:command forResource:"archbom" ofType:NULL];
  383.     strcat(command, " ");
  384.     [self getPath:&command[strlen(command)] forType:"bom"];
  385.     archProcess = [[Subprocess allocFromZone:[self zone]] init:command
  386.                withDelegate:self andPtySupport:NO andStdErr:NO];
  387. }
  388.  
  389. -(void)addArchs:(const char *)string
  390. {
  391.     char result[1024];    // Should be big enough...
  392.     const char *s;
  393.     char *d;
  394.  
  395.     strcpy(result, [packageArchesField stringValue]);
  396.     if ((d = strstr(result, WORKING)) != NULL) {
  397.         *d = '\0';
  398.     }
  399.     else {
  400.         d = result + strlen(result);
  401.     }
  402.     if ((s = string)) {
  403.         do {
  404.             char name[100];
  405.             char *t = name;
  406.  
  407.             while (*s && !NXIsAlNum(*s)) {
  408.                 if (*s == '\n') {
  409.                     *d++ = ' ', s++;
  410.                 }
  411.                 else {
  412.                     *d++ = *s++;
  413.                 }
  414.             }
  415.             while (NXIsAlNum(*s)) *t++ = *s++;
  416.             *t = '\0';
  417.             if (t > name) {
  418. #if DEBUG & 4
  419.                 fprintf(stderr, "addArchs:\"%s\" localized: \"%s\"\n", name, LOCALIZE_ARCH(name));
  420. #endif
  421.                 strcpy(d, LOCALIZE_ARCH(name));
  422.                 d += strlen(d);
  423.             }
  424.         } while (*s);
  425.  
  426.         strcpy(d, WORKING);
  427.     }
  428.     [packageArchesField setStringValue:result];
  429.     [window displayIfNeeded]; // necessary??
  430. }
  431.  
  432. -subprocess:(Subprocess *)sender output:(char *)buffer
  433. {
  434.     if (sender == archProcess) {
  435.         [self addArchs:buffer];
  436.     }
  437.     return self;
  438. }
  439.  
  440. -subprocessDone:(Subprocess *)sender
  441. {
  442.     if (sender == archProcess) {
  443.         archProcess = nil;
  444.         [self addArchs:NULL];
  445.     }
  446.     [sender free];
  447.     return self;
  448. }
  449.  
  450. static void openInWorkspace(const char *filename)
  451. {
  452.     // Indirect approach to circumvent Workspace deadlock/timeout.
  453.     char command[14+3*MAXPATHLEN+1];
  454.     const char *s;
  455.     char *d = command;
  456.  
  457.     for (s = "exec open '"; *s; ) *d++ = *s++;
  458.     // Escape single quote characters.
  459.     for (s = filename; *s; ) {
  460.         if ((*d++ = *s++) == '\'') {
  461.             *d++ = '\\', *d++ = '\'', *d++ = '\'';
  462.         }
  463.     }
  464.     for (s = "'&"; *d++ = *s++; ) ;
  465.     system(command);
  466. }
  467.  
  468. -open:sender
  469. {
  470.     openInWorkspace([package directory]);
  471.     return self;
  472. }
  473.  
  474. @end
  475.  
  476. /*======================================================================
  477.  * PackageInspector.m,v
  478.  * Revision 1.9  1996/03/18 22:02:53  tom
  479.  * (VERSION): 0.96 -- prepare for release;
  480.  * (-load): check for gzip_package'd package, use appropriate sizes format string.
  481.  *
  482.  * Revision 1.8  1995/09/01 21:46:27  tom
  483.  * Circumvent open deadlock/timeout (when Installer.app is not yet launched);
  484.  * (openInWorkspace): new private function; (-open:): new method.
  485.  *
  486.  * Revision 1.7  1995/07/30 22:20:26  tom
  487.  * (LOCALIZE_ARCH): new macro; (-addArchs:): new method;
  488.  * (-subprocess:output:,-subprocessDone:) use it.
  489.  *
  490.  * Revision 1.6  1995/07/30 16:59:51  tom
  491.  * import Subprocess.h; (archProcess): new ivar;
  492.  * (-getArchs,-subprocess:output:,-subprocessDone:): new methods;
  493.  * added for asynchronous arch-determination.
  494.  *
  495.  * Revision 1.5  1995/07/29 19:13:35  tom
  496.  * (+new): avoid reassignment of self;
  497.  *  make packageDescriptionText vertically resizable;
  498.  * (-shouldLoad): rewritten to generalized array-driven approach.
  499.  *
  500.  * Revision 1.4  1995/04/02 02:39:01  tom
  501.  * (package): NXBundle instead of (const char *). so that localized info files
  502.  *  are found. (this loses out if *.pkg is a symbolic link, though.)
  503.  *
  504.  * Revision 1.3  1994/12/07 00:00:36  tom
  505.  * (RCSid): add spaces.
  506.  *
  507.  * Revision 1.2  1994/11/25 21:27:18  tom
  508.  * (package ivar): use (char*) instead of (NXBundle*) to workaround symlink problems
  509.  *
  510.  * Revision 1.1  1994/11/25 16:13:12  tom
  511.  * Initial revision
  512.  *
  513.  *======================================================================*/
  514.